package com.kgcx.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.TypeUtils;
import com.kgcx.common.annotation.validator.*;
import com.kgcx.common.aspect.utils.AspectUtils;
import com.kgcx.common.bean.RequestData;
import com.kgcx.common.bean.ReturnData;
import com.kgcx.common.client.CommonFallback;
import com.kgcx.common.constant.Cons;
import com.kgcx.common.exception.BusinessException;
import com.kgcx.common.utils.ReflectHelper;
import com.kgcx.common.utils.TokenUtils;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import com.xiaoleilu.hutool.util.ReflectUtil;
import feign.codec.DecodeException;
import io.jsonwebtoken.Claims;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @author wuganglin
 * Created by Administrator on 2017/2/20.
 */
@Component
@Aspect
@SuppressWarnings("unchecked")
public class ValidateAndLogAspect {

    protected Logger logger = LoggerFactory.getLogger(this.getClass());


    @Pointcut("execution(public * com.kgcx.*.controller.*.*(..)) ")
    public void pointcut() {
    }

    //应用周围通知
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint call) {
        try {
            Signature signature = call.getSignature();
            /*if (!(signature instanceof MethodSignature)) {
                throw new IllegalArgumentException("该注解只能用于方法");
            }*/
            MethodSignature msig = (MethodSignature) signature;
            Object target = call.getTarget();
            Method method = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());

            String methodName = target.getClass().getSimpleName() + "." + method.getName() + "()";
            RequestData rd = AspectUtils.getRequestData(call, msig);
            AspectUtils.findRequestBody(call, method, rd);
            //logBegore日志
            logBefore(target, methodName, rd);


            //执行校验器
            ReturnData returnData = validate(call, method, rd);

            Object result = returnData;
            //无校验失败信息,则正常执行方法
            if (null == returnData) {

                result = call.proceed();

            }
            //logAfter日志
            logAfter(target, methodName, result);

            return result;
        } catch (Throwable e) {
            if (e instanceof HystrixRuntimeException) {
                return CommonFallback.commonFallBack(e.getMessage());
            }
            if (e instanceof BusinessException) {
                BusinessException ex = (BusinessException) e;
                throw ex;
            }
            if (e instanceof DecodeException) {
                return new ReturnData("-1", e.getMessage());
            }
            logger.error("内部错误", e);
            return new ReturnData("-1", "内部错误！");
        }
    }


    private void logError(Object target, Exception e) {
        try {
            Method logError = target.getClass().getMethod("logError", Exception.class);
            if (null != logError) {
                logError.invoke(target, e);
            }
        } catch (Exception ex) {
            logger.error(target.getClass().getSimpleName() + "执行logError方法失败", ex);
        }
    }

    private void logAfter(Object target, String methodName, Object returnData) {
        try {
            //执行after日志方法,打印方法名与返回结果
            Method logAfter = target.getClass().getMethod("logAfter", String.class);
            if (null != logAfter) {
                logAfter.invoke(target, methodName + "---" + JSONObject.toJSONString(returnData));
            }
        } catch (Exception e) {
            logger.error(target.getClass().getSimpleName() + "执行logAfter方法失败", e);
        }
    }

    private void logBefore(Object target, String methodName, RequestData rd) {
        try {
            //执行before日志方法打印方法名和请求参数
            Method logBefore = target.getClass().getMethod("logBefore", String.class);
            if (null != logBefore) {
                logBefore.invoke(target, methodName + "---" + rd.toString());
            }
        } catch (Exception e) {
            logger.error(target.getClass().getSimpleName() + "执行logBefore方法失败", e);
        }
    }

    /**
     * 校验bean对象属性
     *
     * @param field
     * @param target
     * @param parentName
     * @return
     */
    private ReturnData validateBean(Field field, Object target, String parentName) {

        Object fieldTarget = ReflectHelper.getFieldValue(target, field.getName());
        //如果属性为ApiModel注解声明,表示需要递归校验
        if (field.getClass().isAnnotationPresent(ApiModel.class)) {
            Field[] childFields = fieldTarget.getClass().getDeclaredFields();
            for (Field childField : childFields) {
                ReturnData validate = validateBean(childField, fieldTarget, fieldTarget.getClass().getSimpleName());
                if (null != validate) {
                    return validate;
                }
            }
        }

        ApiModelProperty apiModelProperty = field.getAnnotation(ApiModelProperty.class);
        if (null != apiModelProperty && apiModelProperty.required()) {
            if (null == fieldTarget || StringUtils.isBlank(fieldTarget.toString())) {
                String fieldName = field.getName();
                if (StringUtils.isNotBlank(parentName)) {
                    fieldName = parentName + "." + fieldName;
                }
                return new ReturnData("-1", "json参数为空！" + fieldName);
            }
        }
        return null;
    }

    private ReturnData validate(ProceedingJoinPoint call, Method method, RequestData rd) {

        Object requestBody = rd.get(RequestBody.class.getSimpleName());
        if (null != requestBody) {
            Field[] declaredFields = requestBody.getClass().getDeclaredFields();
            if (ArrayUtils.isNotEmpty(declaredFields)) {
                for (Field declaredField : declaredFields) {
                    ReturnData validate = validateBean(declaredField, requestBody, null);
                    if (null != validate) {
                        return validate;
                    }
                }
            }
        }

        ApiImplicitParams apiImplicitParams = method.getAnnotation(ApiImplicitParams.class);
        if (null != apiImplicitParams) {
            ApiImplicitParam[] implicitParams = apiImplicitParams.value();
            if (ArrayUtils.isNotEmpty(implicitParams)) {
                for (ApiImplicitParam implicitParam : implicitParams) {
                    String name = implicitParam.name();
                    if (StringUtils.isNotBlank(name)) {
                        String value = rd.getString(name);
                        if (implicitParam.required()) {
                            if (StringUtils.isBlank(value)) {
                                return new ReturnData("-1", "参数为空！" + name);
                            }
                        }
                        if (StringUtils.isNotBlank(value)) {
                            Class<?> dataTypeClass = implicitParam.dataTypeClass();
                            if (!dataTypeClass.equals(Void.class)) {
                                try {
                                    Object cast = TypeUtils.cast(value, dataTypeClass, null);
                                    if (null == cast) {
                                        return new ReturnData("-1", "参数类型错误！" + name + ",期望类型为：" + dataTypeClass.getSimpleName());
                                    }
                                } catch (Exception e) {
                                    return new ReturnData("-1", "参数类型错误！" + name + ",期望类型为：" + dataTypeClass.getSimpleName());
                                }
                            }
                        }
                    }
                }
            }
        }


        Validator validator = method.getAnnotation(Validator.class);
        if (null != validator) {

            //非空校验器
            StringValidator[] nullable = validator.string();
            if (ArrayUtils.isNotEmpty(nullable)) {
                for (StringValidator stringValidator : nullable) {
                    String name = stringValidator.value();
                    String value = rd.getString(name);
                    if (StringUtils.isEmpty(value)) {
                        return new ReturnData("-1", "参数为空！" + name);
                    }

                    String[] range = stringValidator.range();
                    if (ArrayUtils.isNotEmpty(range) && !ArrayUtils.contains(range, value)) {
                        return new ReturnData("-1", "参数不在指定范围内！" + name + ",范围：" + ArrayUtils.toString(range));
                    }

                    int maxLength = stringValidator.maxLength();
                    if (0 != maxLength && maxLength < value.length()) {
                        return new ReturnData("-1", "参数长度过长！" + name + ",限长：" + maxLength);
                    }
                }
            }

            //数值校验器
            NumberValidator[] number = validator.number();
            if (ArrayUtils.isNotEmpty(number)) {
                for (NumberValidator numberValidator : number) {
                    String name = numberValidator.value();
                    String valueStr = rd.getString(name);
                    if (StringUtils.isNotBlank(valueStr)) {
                        Long value;
                        try {
                            value = Long.valueOf(valueStr);
                        } catch (NumberFormatException e) {
                            logger.error("valueStr=" + valueStr, e);
                            return new ReturnData("-1", "参数不是数值类型！" + name);
                        }
                        long max = numberValidator.max();
                        if (value > max) {
                            return new ReturnData("-1", "参数偏大！" + name + ",限最大值：" + max);
                        }
                        long min = numberValidator.min();
                        if (value < min) {
                            return new ReturnData("-1", "参数偏小！" + name + ",限最小值：" + min);
                        }
                        long[] range = numberValidator.range();
                        if (ArrayUtils.isNotEmpty(range) && !ArrayUtils.contains(range, value)) {
                            return new ReturnData("-1", "参数不在指定范围内！" + name + ",范围：" + ArrayUtils.toString(range));
                        }
                    }
                }
            }

            //手机号码校验器
            PhoneValidator[] phone = validator.phone();
            if (ArrayUtils.isNotEmpty(phone)) {
                for (PhoneValidator phoneValidator : phone) {
                    String name = phoneValidator.value();
                    String value = rd.getString(name);
                    if (StringUtils.isBlank(value) || !value.matches("^\\d{11}$")) {
                        return new ReturnData("-1", "参数不是电话类型！" + name);
                    }
                }
            }

            //日期格式校验器
            DateValidator[] date = validator.date();
            if (ArrayUtils.isNotEmpty(date)) {
                for (DateValidator dateValidator : date) {
                    String name = dateValidator.value();
                    String value = rd.getString(name);

                    SimpleDateFormat format = new SimpleDateFormat(dateValidator.format());
                    try {
                        // 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期
                        // 比如2007/02/29会被接受，并转换成2007/03/01
                        format.setLenient(false);
                        format.parse(value);
                    } catch (ParseException e) {
                        logger.error("value=" + value + ",format=" + dateValidator.format(), e);
                        // 如果throw java.text.ParseException或者NullPointerException，就说明格式不对
                        return new ReturnData("-1", "参数不是指定的时间类型！" + name + ",指定格式：" + dateValidator.format());
                    }
                }
            }


        }

        IdValidator idValidator = method.getAnnotation(IdValidator.class);
        if (null != idValidator) {
            String[] value = idValidator.value();
            if (ArrayUtils.isNotEmpty(value)) {
                String[] vs = new String[value.length];
                for (int i = 0; i < value.length; i++) {
                    vs[i] = rd.getString(value[i]);
                }

                Object token = ReflectUtil.invoke(call.getTarget(), "getHeader", "token");
                String t = null != token ? token.toString() : null;
                if (StringUtils.isNotBlank(t)) {
                    Claims claims = TokenUtils.getClaims(t);
                    String id = claims.getId();
                    if (!ArrayUtils.contains(vs, id) && TokenUtils.notInnerServer(id) && TokenUtils.notAdminSubject(claims.getSubject())) {
                        return ReturnData.NO_PERMISSION;
                    }
                }

            }
        }
        return null;
    }


    public static void main(String[] s) {
        String ss = "20180325234499";
        SimpleDateFormat format = new SimpleDateFormat(Cons.DATE_FORMAT_YYYYMMDDHHMMSS);
        // 设置lenient为false. 否则SimpleDateFormat会比较宽松地验证日期
        // 比如2007/02/29会被接受，并转换成2007/03/01
        format.setLenient(false);
        try {
            format.parse(ss);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }

}
